package com.tanhua.dubbo.es.api;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.dubbo.config.annotation.Service;
import com.tanhua.dubbo.server.api.UserLocationApi;
import com.tanhua.dubbo.server.pojo.UserLocation;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.dubbo.server.vo.UserLocationVo;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.*;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@Service(version = "1.0.0")
@Slf4j
public class UserLocationApiImpl implements UserLocationApi {

    @Autowired
    private ElasticsearchTemplate esTemplate;
    @PostConstruct
    public void initIndex() {
        //判断索引库是否存在，如果不存在，需要创建
        if (!this.esTemplate.indexExists("tanhua")) {
            // 我们要手动的创建索引
            boolean index = this.esTemplate.createIndex(UserLocation.class);
            if (index) {
                // 创建映射
                this.esTemplate.putMapping(UserLocation.class);
            }
        }

       /* // 判断表是否存在，如果不存在，需要创建
        // type:  通过Java代码创建 默认的type是类名  通过脚本创建 默认是 _doc
        // 一个索引 存储一个类型的东西
        if (!this.esTemplate.typeExists("tanhua", "user_location")) {
            // 创建映射
            this.esTemplate.putMapping(UserLocation.class);
        }*/
    }

    /**
     * 收集用户的位置信息
     * APP 里面设置的是每隔5分钟就会调用一次这个方法(用来更新用户的位置信息)
     *
     * @param userId    用户id
     * @param longitude 经度
     * @param latitude  纬度
     * @param address   地址名称
     * @return
     */
    @Override
    public Boolean updateUserLocation(Long userId, Double longitude, Double latitude, String address) {
        //查询个人的地理位置数据，如果不存在，需要新增，如果是存在数据，更新数据
        try {
            GetQuery getQuery = new GetQuery();
            getQuery.setId(String.valueOf(userId));
            // 根据userId查询这个用户的位置信息是不是已经存在
            UserLocation userLocation = this.esTemplate.queryForObject(getQuery, UserLocation.class);
            // 如果不存在就直接新增一个信息
            if (ObjectUtil.isEmpty(userLocation)) {
                //新增数据
                userLocation = new UserLocation();
                userLocation.setUserId(userId);
                userLocation.setAddress(address);
                userLocation.setCreated(System.currentTimeMillis());
                userLocation.setUpdated(userLocation.getCreated());
                userLocation.setLastUpdated(userLocation.getCreated());
                // APP 端传递过来的是经纬度
                userLocation.setLocation(new GeoPoint(latitude, longitude));

                IndexQuery indexQuery = new IndexQueryBuilder().withObject(userLocation).build();

                //保存数据到ES中
                this.esTemplate.index(indexQuery);
            } else {
                //更新数据
                // 如果使用的是对象更新  务必要把所有的字段都更新 如果哪个字段没有写 就会删除/覆盖
                // 如果使用的是Map更新  只会更新Map中指定的字段

                // 更新的字段
                Map<String, Object> map = new HashMap<>();
                map.put("location", new GeoPoint(latitude, longitude)); // 当前的位置信息
                map.put("updated", System.currentTimeMillis()); // 当前本次的更新时间
                map.put("lastUpdated", userLocation.getUpdated()); // 最后的更新时间(上次更新时间)
                map.put("address", address);  // 位置描述信息

                UpdateRequest updateRequest = new UpdateRequest();
                updateRequest.doc(map);

                // 会覆盖属性  修改之前有4个属性  1个属性
                //updateRequest.doc("对象的JSON字符串格式", XContentType.JSON);

                UpdateQuery updateQuery = new UpdateQueryBuilder()
                        .withId(String.valueOf(userId))  // 更新条件  必须要有用户的ID
                        .withClass(UserLocation.class)   // 表名
                        .withUpdateRequest(updateRequest) // 更新的内容
                        .build();

                // 更新数据
                this.esTemplate.update(updateQuery);
            }

            return true;
        } catch (Exception e) {
            log.error("更新地理位置失败~ userId = " + userId + ", longitude = " + longitude + ", latitude = " + latitude + ", address = " + address, e);
        }

        return false;
    }

    /**
     * 查询用户的位置信息
     *
     * @param userId
     * @return
     */
    @Override
    public UserLocationVo queryByUserId(Long userId) {
        GetQuery getQuery = new GetQuery();
        getQuery.setId(String.valueOf(userId));
        UserLocation userLocation = this.esTemplate.queryForObject(getQuery, UserLocation.class);
        if (ObjectUtil.isNotEmpty(userLocation)) {
            return UserLocationVo.format(userLocation);
        }
        return null;
    }

    /**
     * 根据位置搜索
     *
     * @param longitude 经度
     * @param latitude  纬度
     * @param distance  距离(米)
     * @param page      页数
     * @param pageSize  页面大小
     */
    @Override
    public PageInfo<UserLocationVo> queryUserFromLocation(Double longitude, Double latitude, Double distance, Integer page, Integer pageSize) {
        PageInfo<UserLocationVo> pageInfo = new PageInfo<>();
        pageInfo.setPageNum(page);
        pageInfo.setPageSize(pageSize);

        /*List<UserLocation> locations = esTemplate.queryForList(new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.geoDistanceQuery("location").point(latitude,longitude).distance(distance/1000, DistanceUnit.KILOMETERS))
                .withPageable(PageRequest.of(page-1,pageSize))
                .withSort(SortBuilders.geoDistanceSort("location", latitude,longitude).order(SortOrder.ASC))
                .build(), UserLocation.class);
        return locations;*/



        String fieldName = "location";

        //实现了SearchQuery接口，构造分页、排序
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();

        //分页
        PageRequest pageRequest = PageRequest.of(page - 1, pageSize);
        searchQueryBuilder.withPageable(pageRequest);
        // Bool 查询是多条件的组合查询 或者  与  单条件  根据用户位置查询附近的人
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        //以一个点为中心，指定范围查询
        GeoDistanceQueryBuilder geoDistanceQueryBuilder = new GeoDistanceQueryBuilder(fieldName);
        //中心点
        geoDistanceQueryBuilder.point(new GeoPoint(latitude, longitude));
        //距离（画圆的半径）单位：公里
        geoDistanceQueryBuilder.distance(distance / 1000, DistanceUnit.KILOMETERS);

        boolQueryBuilder.must(geoDistanceQueryBuilder);
        searchQueryBuilder.withQuery(boolQueryBuilder);

        //排序，由近到远排序
        GeoDistanceSortBuilder geoDistanceSortBuilder = new GeoDistanceSortBuilder(fieldName, latitude, longitude);
        geoDistanceSortBuilder.order(SortOrder.ASC); //正序排序
        geoDistanceSortBuilder.unit(DistanceUnit.KILOMETERS); //设置单位
        searchQueryBuilder.withSort(geoDistanceSortBuilder);

        AggregatedPage<UserLocation> aggregatedPage = this.esTemplate.queryForPage(searchQueryBuilder.build(), UserLocation.class);
        if (CollUtil.isEmpty(aggregatedPage.getContent())) {
            return pageInfo;
        }

        pageInfo.setRecords(UserLocationVo.formatToList(aggregatedPage.getContent()));

        /*List<UserLocation> userLocations = elasticsearchTemplate.queryForList(new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.geoDistanceQuery("location").point(latitude, longitude).distance(10d, DistanceUnit.KILOMETERS))
                .withPageable(PageRequest.of(0, 10))
                .withSort(SortBuilders.geoDistanceSort("location", latitude, longitude))
                .build(), UserLocation.class);*/


        return pageInfo;
    }
}
